﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using CloudShot.Properties;
using CloudShot.Settings;
using CloudShot.Utils;
using CloudShot.Utils.Drawings;

namespace CloudShot
{
	internal partial class RecordCaptureForm : Form
	{
		private const int COUNTDOWN = 4;
		private const int DEFAULT_RECORDING_TIME = 10;
		private const int BORDER_SIZE = 4;
		private const int DOUBLE_BORDER_SIZE = BORDER_SIZE * 2;

		private readonly HotkeyListener _hotkeyListener;
		private readonly Timer _timerFollowMouse = new Timer();
		private readonly Timer _timerCapture = new Timer();
		private readonly List<Image> _images = new List<Image>();
		private readonly Point _location;
		private readonly Size _size;
		private readonly string _labelText;
		private readonly Font _labelFont;
		private readonly int _fps;
		private readonly int _recordingTimeInSeconds;
		private readonly string _followMouseHotkey;
		private readonly bool _captureCursor;
		private readonly Stopwatch _stopwatch = new Stopwatch();

		/// <summary>
		/// Some frames was recorded.
		/// </summary>
		private bool _captureStarted = false;
		private float _timeProgress;

		public IEnumerable<Image> Images { get { return _images; } }

		/// <summary>
		/// Play button was pressed.
		/// </summary>
		private bool IsRecordingNow
		{
			get { return _stopwatch.IsRunning; }
		}

		public RecordCaptureForm(Point location, Size size, string labelText, Font labelFont, int fps, CloudShotSettings settings)
		{
			_location = new Point(location.X - BORDER_SIZE, location.Y - BORDER_SIZE);
			_size = size;
			_labelText = labelText;
			_labelFont = labelFont;
			_fps = fps;
			_recordingTimeInSeconds = settings.RecordingTimeInSeconds == 0 ? DEFAULT_RECORDING_TIME : settings.RecordingTimeInSeconds;
			_followMouseHotkey = settings.FollowMouseHotkey;
			_captureCursor = settings.IsCaptureCursorOnGif;

			_hotkeyListener = new HotkeyListener(this);

			InitializeComponent();

			this.FormBorderStyle = FormBorderStyle.None;
			this.DoubleBuffered = true;
			this.SetStyle(ControlStyles.ResizeRedraw, true);

			pnlMainContainer.Location = new Point(BORDER_SIZE, BORDER_SIZE);
			pnlMainContainer.Size = new Size(Width - DOUBLE_BORDER_SIZE, Height - DOUBLE_BORDER_SIZE);
			pnlMainContainer.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
		}

		#region Methods

		private void StartPauseCapturing()
		{
			_timerCapture.Enabled = !_timerCapture.Enabled;

			if (IsRecordingNow)
			{
				_stopwatch.Stop();
				lblCapturer.Invalidate();// update cooldown visualization
			}
			else
			{
				if (!_captureStarted)
					_stopwatch.Reset();

				_stopwatch.Start();
			}

			UpdateRecordPauseStatus();
		}

		private void FinishCapturing()
		{
			_timerCapture.Stop();
			_stopwatch.Stop();
			_stopwatch.Reset();
			DialogResult = DialogResult.OK;
		}

		private void CancelCapturing()
		{
			_timerCapture.Stop();
			_stopwatch.Stop();
			DialogResult = DialogResult.Cancel;
		}

		private Image CaptureOneFrame()
		{
			var rect = lblCapturer.RectangleToScreen(lblCapturer.ClientRectangle);

			Image image = ScreenCapture.CaptureRectangle(rect);

			using (Graphics graphics = Graphics.FromImage(image))
			{
				graphics.DrawPictureLabel(new Rectangle(0, 0, image.Width, image.Height), _labelText, _labelFont);

				if (_captureCursor)
				{
					Point screenCursorPosition;
					Icon cursor = ScreenCapture.CaptureCursorIcon(out screenCursorPosition);

					if (cursor != null)
					{
						Point clientCursorPosition = PointToClient(screenCursorPosition);
						graphics.DrawIcon(cursor, clientCursorPosition.X, clientCursorPosition.Y);
					}
				}
			}

			return image;
		}

		private void UpdateRecordPauseStatus()
		{
			bool useLightImage = btnRecPause.ClientRectangle.Contains(btnRecPause.PointToClient(Cursor.Position));

			if (IsRecordingNow)
				btnRecPause.Image = useLightImage ? Resources.pause_record_light : Resources.pause_record;
			else
				btnRecPause.Image = useLightImage ? Resources.start_record_light : Resources.start_record;

			toolTip.SetToolTip(btnRecPause, IsRecordingNow ? "Pause Recording" : "Resume Recording");
		}

		#endregion

		#region Overridings

		protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
		{
			// This method needed because if set AcceptButton = btnOk and CancelButton = bntCancel, 
			// when press ENTER somehow will be called OnCancelClick.

			if (keyData == Keys.Escape)
			{
				btnCancel.PerformClick();
				return true;
			}

			if (keyData == Keys.Enter)
			{
				btnOk.PerformClick();
				return true;
			}

			if (keyData == Keys.Space)
			{
				btnRecPause.PerformClick();
				return true;
			}

			return base.ProcessCmdKey(ref msg, keyData);
		}

		#endregion

		#region Event Handlers

		private void OnLoad(object sender, EventArgs e)
		{
			// Necessary for ProcessCmdKey
			KeyPreview = true;

			// Set position and size of lblCapturer
			lblCapturer.Location = new Point(1, 1);
			lblCapturer.Size = new Size(Width - (lblCapturer.Location.X * 2), Height - lblCapturer.Location.Y - pnlButtons.Height);

			User32.SetForegroundWindow(Handle);

			// Calculate position of the form
			int x = _location.X - lblCapturer.Location.X;
			int y = _location.Y - lblCapturer.Location.Y;
			int width = _size.Width + Width - lblCapturer.Width;
			int height = _size.Height + Height - lblCapturer.Height;

			// Set position of the form
			Location = new Point(x, y);
			Size = new Size(width, height);

			// Setup timer
			_timerCapture.Interval = 1000 / _fps; //ms
			_timerCapture.Tick += OnTimerCaptureTick;

			_hotkeyListener.RegisterHotkey(ApplicationHotkeys.FollowMouseHotkeyName, _followMouseHotkey, OnToggleMouseFollowing);
			chkFollowMouse.Text = _followMouseHotkey + " - follow mouse";

			_timerFollowMouse.Interval = 100;
			_timerFollowMouse.Tick += (sndr, args) =>
			{
				Location = new Point(Cursor.Position.X - Width / 2, Cursor.Position.Y - Height / 2);
			};

			//StartPauseCapturing(); // Starts recording immediately when form appears.
		}

		private void OnOkClick(object sender, EventArgs e)
		{
			FinishCapturing();
		}

		private void OnPauseClick(object sender, EventArgs e)
		{
			StartPauseCapturing();
		}

		private void OnCancelClick(object sender, EventArgs e)
		{
			CancelCapturing();
		}

		private void OnTimerCaptureTick(object sender, EventArgs eventArgs)
		{
			lblCapturer.Invalidate();

			if (!_captureStarted)
				return;

			_images.Add(CaptureOneFrame());

			float elapsedSeconds = (float)_stopwatch.Elapsed.TotalSeconds - COUNTDOWN;
			_timeProgress = elapsedSeconds * 100f / _recordingTimeInSeconds;
			pnlButtons.Invalidate();// update timer progress visualization

			btnOk.Visible = _captureStarted;

			if (elapsedSeconds >= _recordingTimeInSeconds)
				FinishCapturing();
		}

		private void OnPanelPaint(object sender, PaintEventArgs e)
		{
			const int penWidth = 3;

			using (var progressBg = new Pen(Color.Gray, penWidth))
			using (var progressPen = new Pen(Color.Red, penWidth))
			{
				e.Graphics.DrawLine(progressBg, 0, penWidth / 2, pnlButtons.Width, penWidth / 2);

				float width = pnlButtons.Width * _timeProgress / 100f;
				e.Graphics.DrawLine(progressPen, 0, penWidth / 2, width, penWidth / 2);
			}
		}

		private void OnButtonMouseHover(object sender, EventArgs e)
		{
			var button = (Button)sender;

			if (button == btnCancel)
				button.Image = Resources.cancel_record_light;

			if (button == btnRecPause)
				button.Image = IsRecordingNow ? Resources.pause_record_light : Resources.start_record_light;

			if (button == btnOk)
				button.Image = Resources.done_record_light;
		}

		private void OnButtonMouseLeave(object sender, EventArgs e)
		{
			var button = (Button)sender;

			if (button == btnCancel)
				button.Image = Resources.cancel_record;

			if (button == btnRecPause)
				button.Image = IsRecordingNow ? Resources.pause_record : Resources.start_record;

			if (button == btnOk)
				button.Image = Resources.done_record;
		}

		private void OnToggleMouseFollowing(object sender, EventArgs e)
		{
			chkFollowMouse.Checked = !chkFollowMouse.Checked;
		}

		private void OnMouseFollowingCheckedChanged(object sender, EventArgs e)
		{
			_timerFollowMouse.Enabled = chkFollowMouse.Checked;
		}

		private void OnCapturerPaint(object sender, PaintEventArgs e)
		{
			if (!IsRecordingNow || _captureStarted)
				return;

			int coolDownTime = (int)(COUNTDOWN - _stopwatch.Elapsed.TotalSeconds);
			if (coolDownTime <= 0)
			{
				_captureStarted = true;//TODO it is not good to change state in OnPaint
				return;
			}

			using (var font = new Font("Impact", 128f, FontStyle.Bold, GraphicsUnit.Pixel))
			{
				e.Graphics.DrawOutlineText(coolDownTime.ToString(), font, lblCapturer.Width, lblCapturer.Height, Color.White, Color.Black, 8);
			}
		}

		#endregion

		#region Drag Capture Window

		private bool _drag;
		private Point _startPoint = new Point(0, 0);

		private void OnPanelMouseDown(object sender, MouseEventArgs e)
		{
			_drag = true;
			_startPoint = new Point(e.X, e.Y);
		}

		private void OnPanelMouseMove(object sender, MouseEventArgs e)
		{
			if (!_drag)
				return;

			Point screenMousePosition = PointToScreen(e.Location);
			Location = new Point(screenMousePosition.X - _startPoint.X, screenMousePosition.Y - _startPoint.Y);
		}

		private void OnPanelMouseUp(object sender, MouseEventArgs e)
		{
			_drag = false;
		}

		#endregion

		#region Resize Capture Window

		protected override void WndProc(ref Message m)
		{
			const UInt32 WM_NCHITTEST = 0x0084;
			const UInt32 WM_MOUSEMOVE = 0x0200;

			const UInt32 HTLEFT = 10;
			const UInt32 HTRIGHT = 11;
			const UInt32 HTBOTTOMRIGHT = 17;
			const UInt32 HTBOTTOM = 15;
			const UInt32 HTBOTTOMLEFT = 16;
			const UInt32 HTTOP = 12;
			const UInt32 HTTOPLEFT = 13;
			const UInt32 HTTOPRIGHT = 14;

			const int RESIZE_HANDLE_SIZE = 4;

			// Can't resize window after the record was started.
			if (_stopwatch.Elapsed.TotalMilliseconds > 0)
			{
				base.WndProc(ref m);
				return;
			}

			bool handled = false;
			if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
			{
				Size formSize = this.Size;
				Point screenPoint = new Point(m.LParam.ToInt32());
				Point clientPoint = this.PointToClient(screenPoint);

				var boxes = new Dictionary<UInt32, Rectangle>
				{
					{HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
					{HTBOTTOM, new Rectangle(RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
					{HTBOTTOMRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
					{HTRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE)},
					{HTTOPRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
					{HTTOP, new Rectangle(RESIZE_HANDLE_SIZE, 0, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
					{HTTOPLEFT, new Rectangle(0, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
					{HTLEFT, new Rectangle(0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE)}
				};

				foreach (KeyValuePair<UInt32, Rectangle> hitBox in boxes)
				{
					if (hitBox.Value.Contains(clientPoint))
					{
						m.Result = (IntPtr)hitBox.Key;
						handled = true;
						break;
					}
				}
			}

			if (!handled)
				base.WndProc(ref m);
		}

		#endregion
	}
}